//
// Created by martin on 3/26/22.
//

#include "FtpSession.h"
#include "FtpDataServer.h"
#include "FtpDataClient.h"
#include "FtpMessage.h"
#include "FtpRequest.h"
#include "UserDao.h"
#include "Filesystem.h"
//#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/base/Logging.h"

#include <functional>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <iostream>

using std::string;
using std::pair;
using std::vector;
using ftp::filesystem::IoFile;

using namespace ftp;
using namespace muduo;
using namespace muduo::net;


const std::string FtpSession::kFtpRootDirectory = "/";

FtpSession::FtpSession(const muduo::net::TcpConnectionPtr& conn, const UserDao& userDao)
	: loop_(CHECK_NOTNULL(conn->getLoop())),
	ftpUserDao_(userDao),
	ftpCurrentWorkingDirectory_(kFtpRootDirectory),
	dataBinaryFlag_(true),
	portFlag_(true),
	receiveFileErrorFlag_(false),
	conntrolConnWeakPtr_(conn),
	isTransferData_(false)
{
	LOG_INFO << "Create new FtpSession object@" << this;
}

FtpSession::~FtpSession()
{
	LOG_INFO << "Destroy FtpSession object@" << this;
}

void FtpSession::start()
{
	sendFtpMessage(FtpMessage(FtpReplyCode::k220ServiceReadyForNewUser, "Welcome to Easy-Ftp Server"));
}

void FtpSession::sendFtpMessage(FtpReplyCode code, const std::string& message)
{
	sendFtpMessage(FtpMessage(code, message));
}

void FtpSession::sendFtpMessage(const FtpMessage& message)
{
	sendRawMessage(message.str());
}

void FtpSession::sendRawMessage(const std::string& rawMessage)
{
	auto controlConn = conntrolConnWeakPtr_.lock();
	if (controlConn) {
		controlConn->send(rawMessage);
	}

	LOG_INFO << "-->" << rawMessage.substr(0, rawMessage.size() - 2);
}

void FtpSession::handleFtpCommand(const FtpRequest& request)
{
	std::string ftpCommand = request.commandName();
	std::string ftpParam = request.commandParam();

	// Find out the command in the handlers_, then callback the handler if command exists;
	// If not exist, reply ftp code 500 to tell the client that the server can't recognize the command.
    auto item = std::find_if(handlers_.begin(), handlers_.end(), [&ftpCommand](HandlerItemType const &e) {
        return ::strcasecmp(ftpCommand.c_str(), e.first) == 0;
    });
    if (item == handlers_.end()) {
        sendFtpMessage(FtpReplyCode::k500SyntaxErrorUnrecognizedCommand, "Unrecognized command");
    }
    else {
        auto const handler = item->second;
        (this->*handler)(ftpParam); // Callback command handler
        lastCommand_ = ftpCommand;
    }
}


//---------------------------------------------------------
// Handle Ftp Commands
//---------------------------------------------------------

// Access control commands

void FtpSession::handleFtpCommandUSER(const std::string& param)
{
	loggedInUser_ = nullptr;
	usernameForLogin_ = param;
	ftpCurrentWorkingDirectory_ = kFtpRootDirectory; // root fpt path

	if (param.empty()) {
		sendFtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "Please provide username");
	}
	else {
		sendFtpMessage(FtpReplyCode::k331UserNameOkay, "Need password");
	}
}

void FtpSession::handleFtpCommandPASS(const std::string& param)
{
	if (lastCommand_ != "USER") {
		sendFtpMessage(FtpReplyCode::k503CommandsBadSequence, "Login incorrect. Please specify username first");
	}
	else {
		auto user = ftpUserDao_.getUserInfo(usernameForLogin_, param);
		if (user) { // user exist
			loggedInUser_ = user; // save login user info
			sendFtpMessage(FtpReplyCode::k230UserLoggedIn, "Login success");
		}
		else { // user not exist
			sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Login incorrect. Username or password was incorrect");
		}
	}
}

// TODO: ACCT used for special privileged
void FtpSession::handleFtpCommandACCT(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k202CommandNotImplementedSuperfluous, "Command has not been implemented");
}

void FtpSession::handleFtpCommandCWD(const std::string& param)
{
	sendFtpMessage(executeCWD(param));
}

// CDUP = CWD ..
void FtpSession::handleFtpCommandCDUP(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::DirList) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	if (ftpCurrentWorkingDirectory_ != kFtpRootDirectory) { // We are not at the root directory
		// "CDUP" <=> "CWD ..", go to parent directory
		auto cwdReply = executeCWD("..");
		if (cwdReply.replyCode() == FtpReplyCode::k250FileActionCompleted) {
			// CWD return 250 when success, but CDUP return 200 when success
			sendFtpMessage(FtpReplyCode::k200CommandOkay, "CDUP successful. \"" + ftpCurrentWorkingDirectory_ + "\" is current directory");
			return;
		}
		else {
			// CWD return the same code with CDUP when fail
			sendFtpMessage(cwdReply);
			return;
		}
	}
	else { // We are already at the root directory now
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Already at root directory");
		return;
	}
}

void FtpSession::handleFtpCommandREIN(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k502CommandNotImplemented, "Command not implemented");
}

void FtpSession::handleFtpCommandQUIT(const std::string& param)
{
	loggedInUser_ = nullptr; // clear logged in user info

	sendFtpMessage(FtpReplyCode::k221ServiceClosingControlConnection, "Bye. Connection shutting down");
    closeCommand();
}

/// \brief Specifies the host and port to which the server should connect for the next file transfer.
/// This is interpreted as IP address a1.a2.a3.a4, port p1*256+p2.
/// 
/// Default data port 20 can be used in PORT mode on Ftp Server optionally rather than PASV mode.
/// 
/// Command Syntax:
///   PORT a1,a2,a3,a4,p1,p2
/// 
/// \param param: client ip and port info with text format just like "a1,a2,a3,a4,p1,p2"
/// \see http://www.nsftools.com/tips/RawFTP.htm#PORT
void FtpSession::handleFtpCommandPORT(const std::string& param)
{
#if 0
	// TODO
	sendFtpMessage(FtpReplyCode::k500SyntaxErrorUnrecognizedCommand,
		"Ftp active mode is not supported by this server");
#endif

	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	FtpMessage reply = executePort(param);
	if (reply.replyCode() == FtpReplyCode::k200CommandOkay) {
		sendFtpMessage(FtpReplyCode::k200CommandOkay, "PORT command success");
		return;
	}
	else {
		sendFtpMessage(reply);
		return;
	}
}

// PASV: tell server to enter "passive mode". In passive mode, the server will wait for the client 
// to establish a connection with it rather than attempting to connect to a client-specified port. 
// The server will respond with the address of the port it is listening on, with a message like:
// 
//   227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
// 
// where a1.a2.a3.a4 is the IP address and p1*256+p2 is the (data) port number.
// 
// i.e. 192,168,0,108,132,133 means server listen on 192.168.0.108:33925
// 
// http://www.nsftools.com/tips/RawFTP.htm#PASV
void FtpSession::handleFtpCommandPASV(const std::string& param)
{
	// TODO: add by martin 
	// for temporary forbiddance
	//sendFtpMessage(FtpReplyCode::k500SyntaxErrorUnrecognizedCommand,
	//	"Ftp active mode is not supported by this server");

	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	FtpMessage reply = executePasv(param);
	sendFtpMessage(reply);
}

void FtpSession::handleFtpCommandTYPE(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (param == "A") {
		// ASCII
		dataBinaryFlag_ = false;
		sendFtpMessage(FtpReplyCode::k200CommandOkay, "Type set to A");
	}
	else if (param == "I") {
		dataBinaryFlag_ = true;
		sendFtpMessage(FtpReplyCode::k200CommandOkay, "Type set to I");
	}
	else {
		sendFtpMessage(FtpReplyCode::k504CommandNotImplementedForParameter, "Unsupported type");
	}
}

// STRU is obsolete
// \see
// https://cr.yp.to/ftp/type.html
void FtpSession::handleFtpCommandSTRU(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k500SyntaxErrorUnrecognizedCommand, "Unsupported command");
}

// MODE is obsolete
// \see
// https://cr.yp.to/ftp/type.html
void FtpSession::handleFtpCommandMODE(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k500SyntaxErrorUnrecognizedCommand, "Unsupported command");
}

// RETR is used for downloading file/data from server.
// 
// A RETR request asks the server to send the contents of a file over the data connection 
// already established by the client.
// 
// \param param: an encoded pathname of the file. The file is either a binary file or a text file,
// depending on the most recent TYPE request.
//
// \see
// https://cr.yp.to/ftp/retr.html#retr
void FtpSession::handleFtpCommandRETR(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::FileRead) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	std::string targetPath = toAbsoluteFtpPath(param);
	std::string localPath = toLocalPath(param);

	// Check file status
	Filesystem::FileStatus fileStatus(localPath);
	if (!fileStatus.isOk()) { // file not exist
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given file not exist");
		return;
	}

	// User can only download regular file, symbol link using RETR
	auto fileType = fileStatus.type();
	if (fileType != Filesystem::FileType::RegularFile &&
		fileType != Filesystem::FileType::SymbolicLink) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given path is not a valid file");
		return;
	}

	if (!dataConnection_->isOpened()) {// Check data connection
		sendFtpMessage(FtpReplyCode::k425ErrorOpeningDataConnection, "Can't open data connection");
		return;
	}

#if 1 // FIXME: File read-write un-safe
	std::ios::openmode mode = dataBinaryFlag_ ? (std::ios::in | std::ios::binary) : (std::ios::in);
	std::shared_ptr<IoFile> file = std::make_shared<IoFile>(localPath, mode);
	// Check file status with file stream rather than Filesystem::FileStatus, just for convenience.
	if (!file->fileStream_.good()) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "Error opening file for transfer");
		return;
	}

	sendFile(targetPath, file);
#endif
}

void FtpSession::handleFtpCommandSIZE(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::FileRead) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	std::string localPath = toLocalPath(param);

#if 0
	// Use std::ifstream to get file size

	std::ios::openmode openMode = dataBinaryFlag_ ?
		(std::ios::ate | std::ios::in | std::ios::binary) : (std::ios::ate | std::ios::in);
	std::ifstream file(localPath, openMode);

	if (!file.good()) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "SIZE failure. Local error for opening file for size");
		return;
	}

	// RFC 3659 says the returned size should depend on the current STRU, MODE, TYPE parameters, 
	// so the returned size will be exact. But here we use TYPE=I.
	// https://www.rfc-editor.org/rfc/rfc3659.html#page-11
	auto fileSize = file.tellg();
	if (fileSize == std::fstream::pos_type(-1)) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "SIZE failure. Local error for opening file for size");
		return;
	}

	// type cast: std::streampos => std::stringstream => std::string
	std::stringstream ssFileSize;
	ssFileSize << fileSize;

	sendFtpMessage(FtpReplyCode::k213FileStatus, ssFileSize.str());

#else
	// Use Filesystem to get file size

	Filesystem::FileStatus file(localPath);

	if (!file.isOk()) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "SIZE failure. Local error for opening file for size");
		return;
	}

	if (!((file.type() == Filesystem::FileType::RegularFile) ||
		(file.type() == Filesystem::FileType::SymbolicLink)
		)) {
		// Not regular file or symbolic link
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied. Given path is not a file ");
		return;
	}

	auto fileSize = file.fileSize();
	std::string strFileSize = std::to_string(fileSize);
	sendFtpMessage(FtpReplyCode::k213FileStatus, strFileSize);

#endif // 1
}

// STOR used for uploading file from client to server.
// 
// If the server is willing to create a new file under that name, or replace an existing file under that name,
// it responds with a mark using code 150. It then stops accepting new connections, attempts to read 
// the contents of the file from the data connection, and closes the data connection. 
// 
// \param param: an encoded pathname of file on the client, either a binary file or text file
// depending on the most recent TYPE request.
// 
// https://cr.yp.to/ftp/stor.html#stor
void FtpSession::handleFtpCommandSTOR(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	// k550ActionNotTaken do not conform to RFC 959. We use the return code to deny
	// user without FileWrite permission to upload file to the server, as the popular.
	if (static_cast<int>(loggedInUser_->permission_ & Permission::FileWrite) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	std::string targetPath = toAbsoluteFtpPath(param);
	std::string localPath = toLocalPath(param);

	// Check if the given file/directory exists
	auto fileStatus = Filesystem::FileStatus(localPath);
	if (fileStatus.isOk()) { // exists
		auto fileType = fileStatus.type();

		// Regular file exists in the same path and no FileDelete permission
		if (fileType == Filesystem::FileType::RegularFile
			&& static_cast<int>(loggedInUser_->permission_ & Permission::FileDelete) == 0) {
			sendFtpMessage(FtpReplyCode::k553ActionNotTakenFilenameNotAllowed, "File already exists. Permission denied to overwrite file");
			return;
		}
		else if (fileType == Filesystem::FileType::Dir) {
			sendFtpMessage(FtpReplyCode::k553ActionNotTakenFilenameNotAllowed, "Directory with the same name already exists.");
			return;
		}
	}

	std::ios::openmode mode = std::ios::out | std::ios::binary | std::ios::trunc; //dataBinaryFlag_ ? (std::ios::out | std::ios::binary) : (std::ios::out);
	std::shared_ptr<IoFile> file = std::make_shared<IoFile>(localPath, mode);
	if (!file->fileStream_.good()) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "Local error for opening file to transfer");
		return;
	}

	sendFtpMessage(FtpReplyCode::k150FilesStatusOkayOpeningDataConnection, "Opening data channel for file upload to server of " + quotedFtpPath(targetPath));
	receiveFile(targetPath, file);
}

// STOU juse like STOR, only difference is it asks the server to create a file 
// under a new pathname selected by server.
// 
// \param param: it is optional. If it is supplied, it is a suggested pathname,
// which the server will ignore if there is already a file with that pathname.
//
// https://cr.yp.to/ftp/stor.html#stou
void FtpSession::handleFtpCommandSTOU(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	// k550ActionNotTaken do not conform to RFC 959. We use the return code to deny
	// user without FileWrite permission to upload file to the server, as the popular.
	if (static_cast<int>(loggedInUser_->permission_ & Permission::FileWrite) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	//std::string targetPath = toAbsoluteFtpPath(param);
	//std::string localPath = toLocalPath(param);
	std::string uniqueTargetPath = uniqueFilePath(param);
	std::string uniqueLocalPath = toLocalPath(uniqueTargetPath);

	// Check if the given file/directory exists
	auto fileStatus = Filesystem::FileStatus(uniqueLocalPath);
	if (fileStatus.isOk()) { // file exists
		auto fileType = fileStatus.type();

	}

	std::ios::openmode mode = std::ios::out | std::ios::binary | std::ios::trunc; //dataBinaryFlag_ ? (std::ios::out | std::ios::binary) : (std::ios::out);
	std::shared_ptr<IoFile> file = std::make_shared<IoFile>(uniqueLocalPath, mode);
	if (!file->fileStream_.good()) {
		sendFtpMessage(FtpReplyCode::k451ActionAbortedLocalError, "Local error for opening file to transfer");
		return;
	}

	sendFtpMessage(FtpReplyCode::k150FilesStatusOkayOpeningDataConnection, "Opening data channel for unique file upload to server of " + quotedFtpPath(uniqueTargetPath));
	receiveFile(uniqueTargetPath, file);
}

void FtpSession::handleFtpCommandAPPE(const std::string& param)
{
    sendFtpMessage(FtpReplyCode::k502CommandNotImplemented, "Command not implemented");
}

void FtpSession::handleFtpCommandALLO(const std::string& param)
{
	// ALLO is obsolete. The server should accept any ALLO request with code 202.
	// Require server provide enough space to store uploading file. 
	// Take it as NOOP here.
	// https://cr.yp.to/ftp/stor.html
	sendFtpMessage(FtpReplyCode::k202CommandNotImplementedSuperfluous, "Okay");
}

void FtpSession::handleFtpCommandREST(const std::string& param)
{
	// TODO: Reserve
	sendFtpMessage(FtpReplyCode::k502CommandNotImplemented, "Command not implemented");
}

void FtpSession::handleFtpCommandRNFR(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	renameOldPath_.clear();

	auto isRenamableResult = checkIfPathIsRenameable(param);
	if (isRenamableResult.replyCode() == FtpReplyCode::k200CommandOkay) {
		// Given path is renamable
		renameOldPath_ = param;
		sendFtpMessage(FtpReplyCode::k350FileActionNeedFurtherInfo, "Need target name");
		return;
	}
	else {
		sendFtpMessage(isRenamableResult);
		return;
	}
}

void FtpSession::handleFtpCommandRNTO(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (lastCommand_ != "RNFR" || renameOldPath_.empty()) {
		sendFtpMessage(FtpReplyCode::k503CommandsBadSequence, "Please specify target file first");
		return;
	}

	const string& ftpPath = param;
	if (ftpPath.empty()) {
		sendFtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "No target name given");
		return;
	}

	// Check if source path is renamable again
	auto isRenamableResult = checkIfPathIsRenameable(renameOldPath_);
	if (isRenamableResult.replyCode() == FtpReplyCode::k200CommandOkay) { // no error
		// try to rename file from source path (renameOldPath_) to target path (param)
		std::string localSourcePath = toLocalPath(renameOldPath_);
		std::string localTargetPath = toLocalPath(ftpPath);

		// Check if target path is existing
		if (Filesystem::FileStatus(localTargetPath).isOk()) {
			sendFtpMessage(FtpReplyCode::k450FileActionNotTaken, "Target path exists already");
			return;
		}

		if (0 == ::rename(localSourcePath.c_str(), localTargetPath.c_str())) {
			sendFtpMessage(FtpReplyCode::k250FileActionCompleted, "Rename complete");
			return;
		}
		else {
			sendFtpMessage(FtpReplyCode::k450FileActionNotTaken, "Rename file error");
			return;
		}
	}
	else { // error
		sendFtpMessage(isRenamableResult);
		return;
	}
}

/// "ABOR": Abort received ftp command so far. Not recommend.
/// \see https://cr.yp.to/ftp/pipelining.html
void FtpSession::handleFtpCommandABOR(const std::string& param)
{
    if (!isTransferData_) {
        sendFtpMessage(FtpReplyCode::k225DataConnectionOpenNoTransfer, "No transfer to abort");
        return;
    }

    sendFtpMessage(FtpReplyCode::k225DataConnectionOpenNoTransfer, "Aborted");
    sendFtpMessage(FtpReplyCode::k425ErrorOpeningDataConnection, "Transfer aborted");

    closeData();
}

/// DELE: Delete a regular file
void FtpSession::handleFtpCommandDELE(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::FileDelete) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	std::string localPath = toLocalPath(param);
	Filesystem::FileStatus file(localPath);

	if (!file.isOk()) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given file does not exist");
		return;
	}
	else if (file.type() != Filesystem::FileType::RegularFile) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given path is not a file");
		return;
	}
	else {
		// Try to delete the file
		if (0 == ::unlink(localPath.c_str())) { // delete a file successfully
			sendFtpMessage(FtpReplyCode::k250FileActionCompleted, "File deleted successfully");
			return;
		}
		else {
			sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Unable to delete file");
			return;
		}
	}
}

/// RMD: remove an empty directory
void FtpSession::handleFtpCommandRMD(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::DirDelete) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	std::string localPath = toLocalPath(param);
	Filesystem::FileStatus dir(localPath);
	if (!dir.isOk()) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given directory does not exist");
		return;
	}
	else if (dir.type() != Filesystem::FileType::Dir) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given path is not a directory");
		return;
	}
	else if (!dir.canOpenDir()) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Given path is not a valid directory");
		return;
	}

	if (0 == ::rmdir(localPath.c_str())) {
		sendFtpMessage(FtpReplyCode::k200CommandOkay, "Directory delete successfully");
		return;
	}
	else {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Unable to remove directory");
		return;
	}
}

/// MKD: create a new directory
void FtpSession::handleFtpCommandMKD(const std::string& param)
{
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::DirCreate) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	// Certainly, we can also check if the target directory does exist, 
	// or else we can deliver the work to ::mkdir(). Here we choose the latter one.

	auto absolutePath = toAbsoluteFtpPath(param);
	auto localPath = toLocalPath(param);
	mode_t mode = 0755; // 0755: rwxr-xr-x
	if (0 == ::mkdir(localPath.c_str(), mode)) {
		sendFtpMessage(FtpReplyCode::k257PathNameCreated, quotedFtpPath(absolutePath) + " created successfully");
		return;
	}
	else {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Unable to create directory");
		return;
	}
}

void FtpSession::handleFtpCommandPWD(const std::string& /*param*/)
{
	sendFtpMessage(FtpReplyCode::k257PathNameCreated, quotedFtpPath(ftpCurrentWorkingDirectory_) + " is current directory");
}

/// LIST: transfer a list of files in the specified directory.
/// require command sequence: PORT or PASV -> LIST
void FtpSession::handleFtpCommandLIST(const std::string& param)
{
	LOG_INFO << "handleFtpCommandLIST begin";
	if (!authorized()) {
		sendFtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
		return;
	}

	if (static_cast<int>(loggedInUser_->permission_ & Permission::DirList) == 0) {
		sendFtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
		return;
	}

	// Get target path of list directory
	// 
	// Unusual LIST command may like "LIST -l", "LIST -a", "LIST -al", "LIST -a dir", 
	// which looks like ls command on Unix/Linux.
	// Here we need to ignore the command arguments "-l", "-a" etc.
	// 
	// LIST command usual format:
	//   LIST [<SP> <pathname>] <CRLF>
	// 
	// Unusual format:
	//   LIST[<SP><arg>] [<SP><pathname>] <CRLF>
	// 
	//   with:
	//   <arg> ::= -a
	//           | -l
	//           | -al
	//           | -la
	// 
	// https://www.rfc-editor.org/rfc/rfc959.html#page-47

	std::string targetPath;
	// extract target path ignoring param "-a" "-l" "-al" "-la"
	if (param == "-a" || param == "-l" || param == "-al" || param == "-la") {
		targetPath = "";
	}
	else if (param.size() > 3 && param.substr(0, 3) == "-a " || param.substr(0, 3) == "-l ") {
		targetPath = param.substr(3);
	}
	else if (param.size() > 4 && param.substr(0, 4) == "-al " || param.substr(0, 4) == "-la ") {
		targetPath = param.substr(4);
	}
	else {
		targetPath = param;
	}

	targetPath = toAbsoluteFtpPath(targetPath);
	std::string localPath = toLocalPath(targetPath);
	Filesystem::FileStatus dir(localPath);

	if (!dir.isOk()) {
		sendFtpMessage(FtpReplyCode::k450FileActionNotTaken, "Path " + quotedFtpPath(targetPath) + " does not exist");
		return;
	}
	else {
		if (dir.type() != Filesystem::FileType::Dir) {
			sendFtpMessage(FtpReplyCode::k450FileActionNotTaken, "Path does not a directory");
			return;
		}
		else {
			if (!dir.canOpenDir()) {
				sendFtpMessage(FtpReplyCode::k450FileActionNotTaken, "Permission denied");
				return;
			}
			else {
				sendDirectoryListing(targetPath, Filesystem::dirContent(localPath));
				return;
			}
		}
	}
}

/// NLST: Name list. Brief version of LIST.
/// \see http://www.nsftools.com/tips/RawFTP.htm#NLST
void FtpSession::handleFtpCommandNLST(const std::string& param)
{
}

/// SITE: special service for file transfer
void FtpSession::handleFtpCommandSITE(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k202CommandNotImplementedSuperfluous, "Command not implemented");
}

/// SYST: System information.
/// Suggestion: UNIX Type: L8
/// \see http://www.nsftools.com/tips/RawFTP.htm#SYST
void FtpSession::handleFtpCommandSYST(const std::string& param)
{
	// Reply simple string "UNIX Type: L8" to ftp client is enough. Some clients behave in rather strange way
	// when they see particular in the SYST response.
	// 
	// https://cr.yp.to/ftp/syst.html
	sendFtpMessage(FtpReplyCode::k215NameSystemType, "UNIX Type: L8");
}

/// STAT: asks for human-readable information about the server's status.
/// It not necessary to support this command.
///
/// \see https://cr.yp.to/ftp/syst.html
void FtpSession::handleFtpCommandSTAT(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k502CommandNotImplemented, "Command not implemented");
}

/// HELP: asks for human-readable information from the server.
/// Reply system status or help message when param is empty.
/// Reply command help message about specific command when param is a command name.
///
/// Common FTP commands format:
/// https://www.rfc-editor.org/rfc/rfc959.html#page-47
void FtpSession::handleFtpCommandHELP(const std::string& param)
{
	static const vector<pair<string, string>> helpMessageMap = { // <SP> = space (' ', ASCII = 32)
		{"NOOP", "Syntax: NOOP <CRLF>"},
		{"HELP", "Syntax: HELP [<SP> <string>] <CRLF>"},
		{"USER", "Syntax: USER <SP> <username> <CRLF>"},
		{"PASS", "Syntax: PASS <SP> <password> <CRLF>"},
		{"SYST", "Syntax: SYST <CRLF>"},
		{"LIST", "Syntax: LIST [<SP> <pathname>] <CRLF>"},
		{"STAT", "Syntax: STAT [<SP> <pathname>] <CRLF>"},
		{"PWD",  "Syntax: PWD <CRLF>"},
		{"TYPE", "Syntax: TYPE <SP> <type-code> <CRLF> (type-code = A/I)"},
		{"SIZE", "Syntax: SIZE <SP> <pathname>"},
		{"CWD",  "Syntax: CWD <SP> <pathname> <CRLF>"},
		{"CDUP", "Syntax: CDUP <CRLF>"},
		{"MKD",  "Syntax: MKD <SP> <pathname> <CRLF>"},
		{"RMD",  "Syntax: RMD <SP> <pathname> <CRLF>"},
		{"RMDA", "Syntax: RMDA <SP> <pathname> <CRLF>"},
		{"DELE", "Syntax: DELE <SP> <pathname> <CRLF>"},
		{"PASV", "Syntax: PASV <CRLF>"},
		{"PORT", "Syntax: PORT <SP> <host-port> <CRLF>"},
		{"RNFR", "Syntax: RNFR <SP> <pathname> <CRLF>"},
		{"RNTO", "Syntax: RNTO <SP> <pathname> <CRLF>"},

		{"STOR", "Syntax: STOR <SP> <pathname> <CRLF>"},
		{"APPE", "Syntax: APPE <SP> <pathname> <CRLF>"},
		{"STOU", "Syntax: STOU <CRLF>"},
		{"RETR", "Syntax: APPE <sp> file-name"},

		{"QUIT", "Syntax: QUIT <CRLF>"},
		{"MODE", "Syntax: MODE <SP> <mode-code> <CRLF> (mode-code = S:stream/B:block/C:compressed/Z:deflate)"},

		{"XCWD", "Syntax: XCWD <sp> file-path (same with CWD)"},
		{"XCUP", "Syntax: XCUP (same with CDUP)"},
		{"XPWD", "Syntax: XPWD (same with PWD)"},
		{"XMKD", "Syntax: XMKD <SP> <pathname> <CRLF> (same with MKD)"},
	};

	if (param.empty()) {
		// Reply multi-line message to client using code 214 when param is empty.
		std::stringstream ss;
		ss << "214- Easy-Ftp Server help message:\r\n";
		ss << "    Please enter: HELP command-name. Support command-name:\r\n";
		for (const auto& entry : helpMessageMap) {
			ss << "    " << entry.first << "\r\n";
		}
		ss << "214 End of help\r\n";
		sendRawMessage(ss.str());

		return;
	}
	else {
		auto it = std::find_if(helpMessageMap.begin(), helpMessageMap.end(), [&param](const std::pair<string, string> x) {
			return param == x.first;
			});
		if (it != helpMessageMap.end()) {
			sendFtpMessage(FtpReplyCode::k211ReplySystemStatus, it->second);
			return;
		}
		else {
			sendFtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "Command not implemented");
			return;
		}
	}
}

void FtpSession::handleFtpCommandNOOP(const std::string& param)
{
	sendFtpMessage(FtpReplyCode::k200CommandOkay, "Okay");
}

/// FEAT: Modern FTP Commands, Feature (FEAT) Command Syntax.
/// There is no need to support all new commands defined in all future amendments
/// to the FTP protocol. We can tell clients which new commands the server support
/// by listing all supported extension commands in the response.
///
/// \see https://www.rfc-editor.org/rfc/rfc2389.html
void FtpSession::handleFtpCommandFEAT(const std::string& /*param*/)
{
	std::stringstream ss;
	ss << "211- Feature List:\r\n"
		<< "    UTF8\r\n"
		<< "    SIZE\r\n"
		<< "211 End of Feat\r\n";

	sendRawMessage(ss.str());
}

/// OPTS: allows a user-PI to specify the desired behavior of a server - FTP process
/// when another FTP command(the target command) is later issued.
/// The exact behavior, and syntax vary with the target command.
///
/// \see https://www.rfc-editor.org/rfc/rfc2389.html
void FtpSession::handleFtpCommandOPTS(const std::string& param)
{
	std::string upperParam = param;
#if 1 // use for_each to transfer string to upper case string
	std::for_each(upperParam.begin(), upperParam.end(), [](char& c) {
		c = static_cast<char>(::toupper(static_cast<int>(c)));
		});
#else // use transform to transfer string to upper case string
	std::transform(upperParam.begin(), upperParam.end(), upperParam.begin(), [](char c) {
		return static_cast<char>(std::toupper(static_cast<unsigned char>(c))); });
#endif

	if (upperParam == "UTF8 ON") {
		sendFtpMessage(FtpReplyCode::k200CommandOkay, "Okay");
		return;
	}

	sendFtpMessage(FtpReplyCode::k504CommandNotImplementedForParameter, "Unsupported parameter");
}

FtpMessage FtpSession::executeCWD(const std::string& param)
{
	if (!authorized()) {
		return FtpMessage(FtpReplyCode::k530NotLoggedIn, "Not logged in");
	}
	if (static_cast<int>(loggedInUser_->permission_ & Permission::DirList) == 0) {
		return FtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
	}

	if (param.empty()) { // param?
		return FtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "No path given. Please provide a directory path");
	}

	std::string newAbsoluteWorkingDir;
	// Clean path, try to remove "." and ".."
	if (param[0] == '/') { // Absolute path
		newAbsoluteWorkingDir = ftp::Filesystem::simplifyPath(param, '/');
	}
	else { // Relative path
		// Make a new absolute path
		newAbsoluteWorkingDir = Filesystem::simplifyPath(ftpCurrentWorkingDirectory_ + "/" + param, '/');
	}

	auto localPath = toLocalPath(newAbsoluteWorkingDir);
	Filesystem::FileStatus fileStatus(localPath);

	if (!fileStatus.isOk()) {
		return FtpMessage(FtpReplyCode::k550ActionNotTaken, "No such directory or permission denied");
	}
	else if (fileStatus.type() != Filesystem::FileType::Dir) {
		return FtpMessage(FtpReplyCode::k550ActionNotTaken, "Given path is not a directory");
	}
	else if (!fileStatus.canOpenDir()) {
		return FtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
	}

	ftpCurrentWorkingDirectory_ = newAbsoluteWorkingDir;
	return FtpMessage(FtpReplyCode::k250FileActionCompleted, "CWD successful. \"" + ftpCurrentWorkingDirectory_ + "\" is current directory");
}

FtpMessage FtpSession::executePort(const std::string& param)
{
	// e.g. param: "192,168,0,1,135,126" => ip:port = 192.168.0.1:34686
	// port: 135 * 256 + 126 = 34686
	// Number of segment is 6.

	static constexpr size_t kNumsSegment = 6;
	portFlag_ = true;

	if (param.empty()) {
		return FtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "Given address is empty");
	}

	// Extract ip, port info from param string
	const string& ipPortStr = param;
	int ipEndPos = 0;
	int commaCount = 0;
	std::vector<int> digits;
	digits.reserve(kNumsSegment);

	size_t j = 0;
	for (size_t i = 0; i < ipPortStr.size(); ++i) {
		if (ipPortStr[i] == ',') {
			int num = atoi(ipPortStr.substr(j, j - i).c_str());
			j = i + 1;

			digits.push_back(num);
			++commaCount;
		}
		else if (!isdigit(ipPortStr[i])) {
			j = UINT32_MAX; // invalid
			break;
		}
	}
	if (j < ipPortStr.size())
		digits.push_back(atoi(ipPortStr.substr(j).c_str()));

	if (digits.size() != kNumsSegment) { // invalid ip address for PORT
		return FtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "Given address is not a valid address");
	}

	int ip[4] = { digits[0], digits[1], digits[2], digits[3] };
	int port = digits[4] * 256 + digits[5];
	std::stringstream ss;
	ss << ip[0] << '.' << ip[1] << '.' << ip[2] << '.' << ip[3];
	muduo::net::InetAddress serverAddr(ss.str(), static_cast<uint16_t>(port)); // Ftp Data Server address(Ftp Client, rather than Ftp Server)

	auto transferMode = portFlag_ ? TransferMode::PORT : TransferMode::PASV;

	// Open data connection for connecting to Ftp Data Server
    openData(transferMode, serverAddr, std::string("PORTDataConnection"));

	return FtpMessage(FtpReplyCode::k200CommandOkay, "Okay");
}

FtpMessage FtpSession::executePasv(const std::string& param)
{
	portFlag_ = false;
	std::string controlConnName = "ControlConn[unknown]";
	std::string localDataIp = "0.0.0.0";    // any ip, decided by core
	uint16_t localDataPort = 0;             // any port, decided by core

	{
		TcpConnectionPtr controlConnGuard = conntrolConnWeakPtr_.lock();
		if (controlConnGuard) {
			controlConnName = "ControlConn[peer@" + controlConnGuard->peerAddress().toIpPort() + "]";
			localDataIp = controlConnGuard->localAddress().toIp(); // get local ip address from control connection
		}
	}

	auto transferMode = portFlag_ ? TransferMode::PORT : TransferMode::PASV;
	InetAddress addr(localDataIp, localDataPort);

	// Open data connection for listening on data port and accepting Ftp Data Client
    openData(transferMode, addr, std::string("PASVDataConnection"));

	dataConnection_->listen();

	// Check if the data connection is opened/listening, only Ftp Data Server need to listen on local port.
	// Ftp Data Client don't need to do that.
	if (!dataConnection_->isOpened()) {
		return FtpMessage(FtpReplyCode::k421ServiceNotAvailable, "Service is not available. Local error.");
	}

	auto listenAddr = dataConnection_->listenAddress();

	// Reply message for PASV command successful
	std::stringstream ss;
	std::string listenDataIpStr = listenAddr.toIp();
	std::replace(listenDataIpStr.begin(), listenDataIpStr.end(), '.', ',');
	ss << "Entering Passive Mode";
	ss << '(' << listenDataIpStr << ','
		<< std::to_string((listenAddr.port() >> 8) & 0xFF) << ','
		<< std::to_string(listenAddr.port() & 0xFF) << ')';

	return FtpMessage(FtpReplyCode::k227EnteringPassiveMode, ss.str());
}

//---------------------------------------------------------
// Ftp data-socket send
//---------------------------------------------------------

void FtpSession::sendDirectoryListing(const std::string& targetPath, const std::map<std::string, Filesystem::FileStatus>& directoryContents)
{
	// The output should be something like this:
	// -rw------- 1 peter         848 Dec 14 11:22 00README.txt
	// drwxr-xr-x 1 martin martin 512 May 19 10:20 test
	LOG_INFO << "sendDirectoryListing begin";

	if (dataConnection_) {
		auto me = std::weak_ptr<FtpSession>(shared_from_this());

		dataConnection_->setConnectionCallback([me, targetPath, directoryContents](const TcpConnectionPtr& dataConn) {
			auto guard = me.lock();
			if (dataConn->connected()) { // connected
				if (directoryContents.empty()) {
					if (guard) {
						LOG_INFO << "Open data connection successfullly for listing directory" + guard->quotedFtpPath(targetPath);
						guard->sendFtpMessage(FtpReplyCode::k426TransferAborted, "Data transfer aborted");
					}
					return;
				}
				else {
					if (guard) {
						guard->sendFtpMessage(FtpReplyCode::k150FilesStatusOkayOpeningDataConnection,
							"Opening data channel for directory listing of " + guard->quotedFtpPath(targetPath));
					}

					// Create a Unix-style file list by directoryContents (file name, file status)
					std::stringstream ss;
					for (const auto& entry : directoryContents) {
						const std::string& fileName(entry.first);
						const Filesystem::FileStatus& file(entry.second);

						// To avoid show detailed file type to users, so only show 'd' for directory, '-' for other type.
						ss << ((file.type() == Filesystem::FileType::Dir) ? 'd' : '-') << file.permissionString() << "   1 ";
						ss << file.ownerString() << ' ' << file.groupString() << ' ';
						ss << file.fileSize() << ' ' << file.timeString() << ' ';
						ss << fileName;
						ss << "\r\n";
					}

					std::string directoryListStr = ss.str();
					dataConn->send(directoryListStr);
					//dataConn->send(""); // null string means complete end of transmission
				}
			}
			else { // disconnect
				LOG_INFO << "disconnection successfullly for data connection";
				dataConn->shutdown();
				if (guard) {
                    guard->closeData();
				}
			}
			});

		dataConnection_->setWriteCompleteCallback([me](const TcpConnectionPtr& dataConn) {
			auto guard = me.lock();
			if (guard) {
				guard->sendFtpMessage(FtpReplyCode::k226ClosingDataConnection, "Transfer complete. Closing data connection");
			}
			dataConn->shutdown();
			return;
			});

		if (portFlag_) { // PORT mode
			dataConnection_->connect(); // Connect to remote Ftp Data Server (Ftp Client)
		}
		else { // PASV mode
			// Do nothing. Wait for data connection established
		}
	}
	else {
		sendFtpMessage(FtpReplyCode::k425ErrorOpeningDataConnection, "Can't open data connection");
		return;
	}
}

void FtpSession::sendFile(const std::string& targetPath, std::shared_ptr<IoFile> file)
{
	LOG_INFO << "sendFile begin";
	if (dataConnection_) {
		std::weak_ptr<FtpSession> me = shared_from_this();

		dataConnection_->setConnectionCallback([me, targetPath, file](const TcpConnectionPtr& dataConn) {
			std::shared_ptr<FtpSession> guard = me.lock();

			if (dataConn->connected()) {
				LOG_INFO << "Open data connection successfully for sendFile";
				if (!file || !file->fileStream_.is_open() || !file->fileStream_.good()) { // file error
					LOG_INFO << "file error";
					if (guard) {
						guard->sendFtpMessage(FtpReplyCode::k426TransferAborted, "Data transfer aborted. Local file stream error");
					}
					return;
				}
				else { // Okay
					if (guard) {
						guard->sendFtpMessage(FtpReplyCode::k150FilesStatusOkayOpeningDataConnection,
							"Opening data channel for file download from server of " + guard->quotedFtpPath(targetPath));
					}
					dataConn->send(""); // trigger write complete callback
				}
			}
			else { // disconnect
				LOG_INFO << "Close data connection used for sendFile";
				dataConn->shutdown();

				if (guard) {
                    guard->closeData();
				}
			}
			});

		dataConnection_->setWriteCompleteCallback([me, targetPath, file](const TcpConnectionPtr& dataConn) {
			std::shared_ptr<FtpSession> guard = me.lock();

			if (!file) {
				LOG_INFO << "file error: file is null";
				if (guard)
					guard->sendFtpMessage(FtpReplyCode::k426TransferAborted, "Data transfer aborted. file is null");
				dataConn->shutdown();
				return;
			}
			else if (file->fileStream_.eof()) {
				LOG_INFO << "file eof";
				if (guard)
					guard->sendFtpMessage(FtpReplyCode::k226ClosingDataConnection, "Successfully transferred " + guard->quotedFtpPath(targetPath));
				dataConn->shutdown(); // FIXME: unsafe, because client may not receive file data sent by the server.
				return;
			}
			else {
				{ // Read data from file and send to peer with binary type
					std::vector<char> buffer;
					size_t capacity = static_cast<size_t>(64 * 1024); // 64KB
					buffer.reserve(capacity);
                    file->fileStream_.read(buffer.data(), static_cast<long>(buffer.capacity()));
					auto bytesRead = file->fileStream_.gcount();
					//LOG_INFO << "data connection read " << bytesRead << " bytes from file " + me->quotedFtpPath(targetPath);

					dataConn->send(buffer.data(), static_cast<int>(bytesRead));
				}
			}
			});

		if (portFlag_) {
			dataConnection_->connect();
		}
	}
	else {
		sendFtpMessage(FtpReplyCode::k425ErrorOpeningDataConnection, "Local error. Can't open data connection");
		return;
	}
}

void FtpSession::receiveFile(const std::string& targetPath, std::shared_ptr<IoFile> file)
{
	LOG_INFO << "receiveFile begin";
	if (dataConnection_) {
		std::weak_ptr<FtpSession> me = shared_from_this();

		dataConnection_->setConnectionCallback([me, targetPath, file](const TcpConnectionPtr& dataConn) {
			std::shared_ptr<FtpSession> guard = me.lock();

			if (dataConn->connected()) { // connected
				if (!file->fileStream_.is_open()) {
					if (guard) {
						LOG_ERROR << "file stream not open for " << guard->quotedFtpPath(targetPath);
						guard->sendFtpMessage(FtpReplyCode::k426TransferAborted, "Transfer aborted. Local error for opening file");
					}
				}
			}
			else { // disconnect
				if (file->fileStream_.good()) {
					if (guard) {
						guard->sendFtpMessage(FtpReplyCode::k226ClosingDataConnection, "Successfully transferred " + guard->quotedFtpPath(targetPath));
					}

					file->fileStream_.flush();
				}
				dataConn->shutdown();
				if (guard) {
                    guard->closeData();
				}
			}
			});

		dataConnection_->setMessageCallback([me, targetPath, file](const TcpConnectionPtr& dataConn, Buffer* buffer, Timestamp receiveTime) {
			std::shared_ptr<FtpSession> guard = me.lock();

			if (file->fileStream_.fail()) {
				if (guard) {
					LOG_ERROR << "file stream fail for " << guard->quotedFtpPath(targetPath);
					guard->sendFtpMessage(FtpReplyCode::k426TransferAborted, "Transfer aborted. Local error for writing file");
				}
				return;
			}

			//LOG_INFO << "Receive [" << receiveTime.toFormattedString() << "]: " << buffer->readableBytes() << " bytes from Ftp Client";
			file->fileStream_.write(buffer->peek(), static_cast<long>(buffer->readableBytes()));
			buffer->retrieveAll();
			});

		if (portFlag_) {
			dataConnection_->connect();
		}
	}
	else {
		sendFtpMessage(FtpReplyCode::k425ErrorOpeningDataConnection, "Local error. Can't open data connection");
		return;
	}
}


/// \brief Get a unique file path.
///
/// Add an increasing natural number as the suffix of file name before file type suffix(".")
/// suffix number +1 every time.
///
/// \example Relative path
/// 1)"test.log"
///   return "test.log", "test1.log", "test2.", "test3.log", ...
/// 2)"test"
///   return "test", "test1", "test2", "test3", ...
///
/// Absolute path is same with relative path.
///
/// \param filePath: a ftp path of a file, either relative path or absolute path. It is not a local path.
/// \return new unique file path, and it is a ftp path rather than local path.
std::string FtpSession::uniqueFilePath(const std::string& filePath)
{
	std::string absolutePath = toAbsoluteFtpPath(filePath);
	auto dotPos = absolutePath.find_last_of('.');
	std::string fileName, fileType;
	std::string newFilePath;
	std::string newFileName;

	if (dotPos == std::string::npos) { // fail to find out "." means no file type suffix
		fileName = absolutePath;
	}
	else { // success to find out
		fileName = absolutePath.substr(0, dotPos);
		fileType = absolutePath.substr(dotPos + 1);
	}

	newFileName = fileName;
	for (uint64_t i = 0; i < UINT64_MAX; ++i) {
		if (i != 0) newFileName = fileName + std::to_string(i);
		if (!fileType.empty()) {
            newFileName += "." + fileType;
			newFilePath = newFileName;
		}
		else {
			newFilePath = newFileName;
		}

		if (!Filesystem::FileStatus(toLocalPath(newFilePath)).isOk()) {
			break;
		}
	}

	return newFilePath;
}

void FtpSession::closeCommand()
{
    auto conn = conntrolConnWeakPtr_.lock();
    if (conn) {
        conn->shutdown();
    }
}

void FtpSession::openData(TransferMode mode, const muduo::net::InetAddress& serverAddr, const std::string& nameArg)
{
	dataConnection_.reset(new DataConnection(shared_from_this(), loop_, mode, serverAddr, nameArg));
    isTransferData_ = true;
}

void FtpSession::closeData()
{
	if (get_pointer(dataConnection_)) {
		dataConnection_.reset();
        isTransferData_ = false;
	}
}

bool FtpSession::isTransferData() const
{
    return isTransferData_;
}

/// \brief Convert general ftp path to local path which local filesystem can operate directly
///  by local file operation just like fopen(3).
///
/// \param ftpPath general ftp path, it may be a relative path, or a absolute path
/// \return local path
std::string FtpSession::toLocalPath(const std::string& ftpPath)
{
	if (authorized()) {
		// Get the absolute ftp path from ftp path
		std::string absoluteFtpPath = toAbsoluteFtpPath(ftpPath);

		// Map it to local filesystem
		return Filesystem::simplifyNativePath(loggedInUser_->localRootPath_ + "/" + absoluteFtpPath);
	}
	else {
		return "/";
	}
}

/// \brief Convert general ftp path to absolute ftp path
/// \param relativeOrAbsoluteFtpPath general ftp path, may be a relative path, or a absolute path
/// \return absolute ftp path
std::string FtpSession::toAbsoluteFtpPath(const std::string& relativeOrAbsoluteFtpPath)
{
	std::string absolutFtpPath;

	if (!relativeOrAbsoluteFtpPath.empty() && relativeOrAbsoluteFtpPath[0] == '/') { // absolute path
		absolutFtpPath = relativeOrAbsoluteFtpPath;
	}
	else { // relative path
		// Make a new absolut path by relative path based on ftp current working directory
		absolutFtpPath = Filesystem::simplifyPath(ftpCurrentWorkingDirectory_ + "/" + relativeOrAbsoluteFtpPath, '/');
	}
	return absolutFtpPath;
}

FtpMessage FtpSession::checkIfPathIsRenameable(const std::string& ftpPath)
{
	if (ftpPath.empty()) {
		return FtpMessage(FtpReplyCode::k501SyntaxErrorParameters, "No source name given");
	}
	else {
		auto localPath = toLocalPath(ftpPath);
		Filesystem::FileStatus file(localPath);

		if (!file.isOk()) { // Can not open the file
			return FtpMessage(FtpReplyCode::k550ActionNotTaken, "File does not exist");
		}
		else {
			// Check permission
			Permission requiredPermission;
			if (file.type() == Filesystem::FileType::Dir) {
				requiredPermission = Permission::DirRename;
			}
			else {
				requiredPermission = Permission::FileRename;
			}

			if ((loggedInUser_->permission_ & requiredPermission) != requiredPermission) {
				return FtpMessage(FtpReplyCode::k550ActionNotTaken, "Permission denied");
			}
			else {
				return FtpMessage(FtpReplyCode::k200CommandOkay, "");
			}
		}
	}
}

/// \brief Quoted ftp path by add "" around ftp path
/// \example /home/user => "/home/user"
/// \param ftpPath ftp path, it can be a relative path or absolute path.
/// \return Quoted path with text format.
std::string FtpSession::quotedFtpPath(const std::string& ftpPath)
{
	std::string quotedStr;
	quotedStr.reserve(ftpPath.size());
	quotedStr.push_back('\"');

	for (char c : ftpPath) {
		quotedStr.push_back(c);
		if ('\"' == c) // dont omit orignal quote
			quotedStr.push_back(c);
	}
	quotedStr.push_back('\"');

	return quotedStr;
}

/// Check if the user logged in
inline bool FtpSession::authorized() const
{
    return loggedInUser_ != nullptr;
}

std::vector<FtpSession::HandlerItemType> const FtpSession::handlers_ = {
        // Access control commands
        { "USER", &FtpSession::handleFtpCommandUSER },
        { "PASS", &FtpSession::handleFtpCommandPASS },
        { "ACCT", &FtpSession::handleFtpCommandACCT },
        { "CWD",  &FtpSession::handleFtpCommandCWD },
        { "XCWD", &FtpSession::handleFtpCommandCWD }, // RFC 1123
        { "CDUP", &FtpSession::handleFtpCommandCDUP },
        { "REIN", &FtpSession::handleFtpCommandREIN },
        { "QUIT", &FtpSession::handleFtpCommandQUIT },

        // Transfer parameter commands
        { "PORT", &FtpSession::handleFtpCommandPORT },
        { "PASV", &FtpSession::handleFtpCommandPASV },
        { "TYPE", &FtpSession::handleFtpCommandTYPE },
        { "STRU", &FtpSession::handleFtpCommandSTRU },
        { "MODE", &FtpSession::handleFtpCommandMODE },

        // Ftp service commands
        { "RETR", &FtpSession::handleFtpCommandRETR },
        { "STOR", &FtpSession::handleFtpCommandSTOR },
        { "STOU", &FtpSession::handleFtpCommandSTOU },
        { "APPE", &FtpSession::handleFtpCommandAPPE },
        { "ALLO", &FtpSession::handleFtpCommandALLO },
        { "REST", &FtpSession::handleFtpCommandREST },
        { "RNFR", &FtpSession::handleFtpCommandRNFR },
        { "RNTO", &FtpSession::handleFtpCommandRNTO },
        { "ABOR", &FtpSession::handleFtpCommandABOR },
        { "DELE", &FtpSession::handleFtpCommandDELE },
        { "RMD",  &FtpSession::handleFtpCommandRMD },
        { "XRMD", &FtpSession::handleFtpCommandRMD }, // RFC 1123
        { "MKD",  &FtpSession::handleFtpCommandMKD },
        { "XMKD", &FtpSession::handleFtpCommandMKD }, // RFC 1123
        { "PWD",  &FtpSession::handleFtpCommandPWD },
        { "XPWD", &FtpSession::handleFtpCommandPWD }, // RFC 1123
        { "LIST", &FtpSession::handleFtpCommandLIST },
        { "NLST", &FtpSession::handleFtpCommandNLST },
        { "SITE", &FtpSession::handleFtpCommandSITE },
        { "SYST", &FtpSession::handleFtpCommandSYST },
        { "STAT", &FtpSession::handleFtpCommandSTAT },
        { "HELP", &FtpSession::handleFtpCommandHELP },
        { "NOOP", &FtpSession::handleFtpCommandNOOP },

        // Modern FTP Commands
        { "FEAT", &FtpSession::handleFtpCommandFEAT },
        { "OPTS", &FtpSession::handleFtpCommandOPTS },
        { "SIZE", &FtpSession::handleFtpCommandSIZE },
};
